 1.Spark原理之作业执行原理中调度策略
   
   TaskScheduler会先把 DAGScheduler 给过来的 TaskSet 封装成 TaskSetManager 扔到任务队列里，然后再从任务队
列里按照一定规则把它们取出来，由 SchedulerBackend 发送给Executor运行；
   TaskScheduler以树的方式来管理任务队列，树中的节点类型为 Schedulable，叶子节点为 TaskSetManager，非叶
子节点为Pool：
   TaskScheduler 支持两种调度策略：FIFO(默认调度策略)、FAIR。
   1).FIFO 调度策略
   FIFO调度策略，TaskSetManager 按照到来的先后次序进入队列；出队时直接拿最先进入队列的 TaskSetManager。
   FIFO调度策略是默认模式，在此模式下，只有一个 TaskSetManager 池。
   2).Fair 调度策略
   Fair 模式中有一个 rootPool 和多个子 Pool，各个子 Pool中存储着所有待分配的 TaskSetManager ；
   在 Fair 模式中，需要先对子 Pool 进行排序，再对子 Pool 里面的 TaskSetManager 进行排序，因为Pool和
TaskSetManager 都继承了 Schedulable trait，因此可使用相同的排序算法；
   每个要排序的对象包含三个属性:
       runningTasks值（正在运行的Task数）
       minShare值（时间）
       weight值（权重）
   比较时会综合考量 runningTasks 值，minShare 值以及 weight 值。
   备注：minShare、weight的值在配置文件 fairscheduler.xml 中被指定。
   
   调度池在构建阶段会先读取 fairscheduler.xml （$SPARK_HOME/conf）文件的相关配置，然后进行比较：
       如果A对象的 runningTasks > minShare，B对象的 runningTasks < minShare，那么B排在A前面；
(runningTasks比minShare小的先执行)
       如果A、B对象的 runningTasks < minShare，那么就比较 runningTasks 与 minShare 的比值，谁小谁排前
面；(使用率低的先执行)
       如果A、B对象的 runningTasks > minShare，那么就比较runningTasks与weight的比值（权重使用率），谁小
谁排前面。(权重使用率低的先执行)
       如果上述比较均相等，则比较名字
   整体上来说就是通过 minShare 和 weight 这两个参数控制比较过程，做到让 minShare 使用率和权重使用率少（实
际运行task比例较少）的先运行。
   FAIR模式排序完成后，所有的 TaskSetManager被放入一个ArrayBuffer 里，之后依次被取出并发送给Executor执行。
   从调度队列中拿到 TaskSetManager 后，由于 TaskSetManager 封装了一个 Stage 的所有 Task，并负责管理调度这
些 Task，那么接下来的工作就是 TaskSetManager 按照一定的规则一个个取出 Task 给 TaskScheduler，TaskScheduler
再交给 SchedulerBackend ，最终分发到Executor上执行。

 2.本地化调度
   
   DAGScheduler切割Job，划分Stage。调用submitStage来提交一个Stage对应的tasks，submitStage会调用
submitMissingTasks，submitMissingTasks 确定每个需要计算的 task 的 preferred Locations
   通过调用 getPreferrdeLocations 得到分区的优先位置，一个partition对应一个task，此分区的优先位置就是
task的优先位置
   从调度队列中拿到 TaskSetManager 后，那么接下来的工作就是 TaskSetManager 按照一定的规则一个个取出
task 给 TaskScheduler，TaskScheduler 再交给 SchedulerBackend 发送到 Executor 上执行
   根据每个 task 的优先位置，确定 task 的 Locality 级别，Locality一共有五种，优先级由高到低顺序
       PROCESS_LOCAL data is in the same JVM as the running code. This is the best locality possible
	   NODE_LOCAL data is on the same node. Examples might be in HDFS on the same node, or in
another executor on the same node. This is a little slower than PROCESS_LOCAL because the data
has to travel between processes
       NO_PREF data is accessed equally quickly from anywhere and has no locality preference
	   RACK_LOCAL data is on the same rack of servers. Data is on a different server on the same rack so
needs to be sent over the network, typically through a single switch
       ANY data is elsewhere on the network and not in the same rack
   在调度执行时，Spark总是会尽量让每个 Task 以最高的本地性级别来启动；
   当某个 Task 以某个本地性级别启动，但是该本地性级别对应的所有节点都没有空闲资源而启动失败，此时并不会马
上降低本地性级别启动，而是在某个时间长度内再次以X本地性级别来启动该task，若超过限时时间则降级启动，去尝试
下一个本地性级别，依次类推；
   通过调整每个类别的最大容忍延迟时间，在等待阶段对应的 Executor 可能就会有相应的资源去执行此 Task，这样有
可能在一定程度上提到运行性能；
 
 3.返回结果
   
   对于Executor的计算结果，会根据结果的大小使用不同的处理策略：
       计算结果在（0，200KB-128 MB)区间内：通过Netty直接发送给Driver终端
	       Netty的预留空间reservedSizeBytes，200KB
           spark.rpc.message.maxSize，默认值是128MB
	   计算结果在[128MB， 1GB]区间内：将结果以 taskId 为编号存入到 BlockManager 中，然后通过 Netty 把编号
发送给 Driver；阈值可通过 Netty 框架传输参数设置
           spark.driver.maxResultSize，默认值 1G
       计算结果在(1GB，∞)区间内：直接丢弃，可通过spark.driver.maxResultSize配置
 
 4.失败重试与黑名单机制
   
   Task被提交到Executor启动执行后，Executor会将执行状态上报给SchedulerBackend(DriverEndpoint)；
   SchedulerBackend 则告诉 TaskScheduler，TaskScheduler 找到该 Task 对应的 TaskSetManager，并通知到该
TaskSetManager，这样 TaskSetManager 就知道 Task 的失败与成功状态；
   
   即 SchedulerBackend(DriverEndPoint) => TaskScheduler => TaskSetManager
   
   对于失败的Task，会记录它失败的次数，如果失败次数还没有超过最大重试次数，那么就把它放回待调度的Task池子
中，否则整个Application失败；
   
   在记录 Task 失败次数过程中，会记录它上一次失败所在的Executor Id和Host。下次再调度这个Task时，会使用黑名
单机制，避免它被调度到上一次失败的节点上，起到一定的容错作用；
   